// (c) Copyright 2013-2014 Stephen Gold <sgold@sonic.net>
// Distributed under the terms of the GNU General Public License

/*
 This file is part of the Bats Game.

 The Bats Game is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by the
 Free Software Foundation, either version 3 of the License, or (at your
 option) any later version.

 The Bats Game is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.

 You should have received a copy of the GNU General Public License
 along with the Bats Game.  If not, see <http://www.gnu.org/licenses/>.
 */
package bats.ui;

import bats.scene.PointOfView;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.MouseButtonTrigger;
import java.util.logging.Level;
import java.util.logging.Logger;
import jme3utilities.MyString;
import jme3utilities.ui.InputMode;

/**
 * An input mode which corresponds to a game mode: it has a secondary (long)
 * name and manages the mouse buttons.
 *
 * @author Stephen Gold <sgold@sonic.net>
 */
public class GameInputMode
        extends InputMode {
    // *************************************************************************
    // constants

    /**
     * message logger for this class
     */
    final private static Logger logger =
            Logger.getLogger(GameInputMode.class.getName());
    // *************************************************************************
    // fields
    /**
     * corresponding game mode: set by constructor
     */
    final private GameMode gameMode;
    /**
     * long-form name of this mode: set by constructor
     */
    final private String longName;
    // *************************************************************************
    // constructors

    /**
     * Instantiate a disabled mode. After instantiation, the first method
     * invoked should be initialize().
     *
     * @param gameMode corresponding game mode (not null)
     * @param shortName short name for the mode (not null)
     * @param longName long name for the mode (not null)
     */
    GameInputMode(GameMode gameMode, String shortName, String longName) {
        super(shortName);

        assert gameMode != null;
        this.gameMode = gameMode;

        assert longName != null;
        this.longName = longName;
    }
    // *************************************************************************
    // new methods exposed

    /**
     * Access the corresponding game mode.
     *
     * @return pre-existing instance
     */
    public GameMode getGameMode() {
        return gameMode;
    }

    /**
     * Read the long-form name of this mode.
     */
    public String getLongName() {
        return longName;
    }
    // *************************************************************************
    // ActionListener methods

    /**
     * Process an action from the GUI or keyboard.
     *
     * @param actionString textual description of the action (not null)
     * @param ongoing true if the action is ongoing, otherwise false
     * @param ignored
     */
    @Override
    public void onAction(String actionString, boolean ongoing, float ignored) {
        /*
         * Ignore actions which are not ongoing.
         */
        if (!ongoing) {
            return;
        }

        logger.log(Level.INFO, "Got action {0}", MyString.quote(actionString));
        if (!Actions.isValidString(actionString)) {
            logger.log(Level.WARNING, "Invalid action string {0}",
                    MyString.quote(actionString));
        }

        if (actionString.equals("close")) {
            simpleApplication.stop();
            return;
        }
        /*
         * Split the action string into words.
         */
        String[] words = actionString.split("\\s+");
        assert words.length > 0 : MyString.quote(actionString);

        boolean wasHandled = gameMode.processAction(words);
        if (!wasHandled) {
            logger.log(Level.WARNING, "Action {0} was not handled.",
                    MyString.quote(actionString));
        }
    }
    // *************************************************************************
    // InputMode methods

    /**
     * Add the default hotkey bindings. These are the bindings which will be
     * used if no custom bindings are found.
     */
    @Override
    protected void defaultBindings() {
        /*
         * Windows uses:
         *  KEY_LMETA to open the Windows menu
         *  KEY_NUMLOCK to toggle keypad key definitions
         * SimpleApplication pre-assigns:
         *  KEY_F5 to toggle the stats display
         *  KEY_C to print camera coordinates
         *  KEY_M to print memory statistics
         * The Nifty GUI uses:
         *  KEY_TAB for navigation
         *  KEY_RETURN for selection
         *  KEY_SPACE to reset the GUI
         * And com.jme3.app.state.ScreenshotAppState uses:
         *  KEY_SYSRQ to save a screenshot
         *
         * For now, avoid re-assigning those hotkeys.
         */
        bind("close", KeyInput.KEY_ESCAPE);
        bind("print map", KeyInput.KEY_O);
        bind("print pickables", KeyInput.KEY_P);
        bind("print root", KeyInput.KEY_G);
        bind("print scene", KeyInput.KEY_I);
        bind("signal alt", KeyInput.KEY_LMENU);
        bind("signal alt", KeyInput.KEY_RMENU);
        bind("signal shift", KeyInput.KEY_LSHIFT);
        bind("signal shift", KeyInput.KEY_RSHIFT);
        bind("toggle 3DCursor", KeyInput.KEY_COMMA);
        bind("toggle axes", KeyInput.KEY_PERIOD);
        bind("toggle ground", KeyInput.KEY_Y);
        bind("toggle hud", KeyInput.KEY_H);
        bind("toggle physics", KeyInput.KEY_B);
        bind("toggle target all", KeyInput.KEY_V);
        bind("view horizontal", KeyInput.KEY_NUMPAD1);
        bind("view overhead", KeyInput.KEY_NUMPAD7);

        switch (shortName) {
            case "build":
                bind("add block", KeyInput.KEY_EQUALS);
                bind("delete picked", KeyInput.KEY_DELETE);
                bind("duplicate picked", KeyInput.KEY_INSERT);
                bind("rotate left", KeyInput.KEY_A);
                bind("rotate right", KeyInput.KEY_D);
                bind("step back", KeyInput.KEY_S);
                bind("step down", KeyInput.KEY_PGDN);
                bind("step forward", KeyInput.KEY_W);
                bind("step left", KeyInput.KEY_Z);
                bind("step right", KeyInput.KEY_X);
                bind("step up", KeyInput.KEY_PGUP);
                bind("step viewBottom", KeyInput.KEY_DOWN);
                bind("step viewLeft", KeyInput.KEY_LEFT);
                bind("step viewRight", KeyInput.KEY_RIGHT);
                bind("step viewTop", KeyInput.KEY_UP);
                bind("toggle terrain", KeyInput.KEY_T);
                break;

            case "pose":
                bind("signal turnLeft", KeyInput.KEY_A);
                bind("signal turnRight", KeyInput.KEY_D);
                bind("toggle skeleton", KeyInput.KEY_N);
                break;

            case "sim":
                bind("add fire", KeyInput.KEY_F);
                bind("add unit", KeyInput.KEY_EQUALS);
                bind("delete flames", KeyInput.KEY_E);
                bind("delete picked", KeyInput.KEY_DELETE);
                bind("impair", KeyInput.KEY_K);
                bind("signal advance", KeyInput.KEY_W);
                bind("signal loadLaunch", KeyInput.KEY_L);
                bind("signal retreat", KeyInput.KEY_S);
                bind("signal stepLeft", KeyInput.KEY_Z);
                bind("signal stepRight", KeyInput.KEY_X);
                bind("signal stepViewBottom", KeyInput.KEY_DOWN);
                bind("signal stepViewLeft", KeyInput.KEY_LEFT);
                bind("signal stepViewRight", KeyInput.KEY_RIGHT);
                bind("signal stepViewTop", KeyInput.KEY_UP);
                bind("signal turnLeft", KeyInput.KEY_A);
                bind("signal turnRight", KeyInput.KEY_D);
                bind("toggle aggressive", KeyInput.KEY_Q);
                bind("toggle pause", KeyInput.KEY_PAUSE);
                bind("toggle skeleton", KeyInput.KEY_N);
                bind("toggle terrain", KeyInput.KEY_T);
                break;

            default:
                assert false : shortName;
        }
    }

    /**
     * Enable or disable this mode.
     *
     * @param newState true to enable, false to disable
     */
    @Override
    public void setEnabled(boolean newState) {
        boolean oldState = isEnabled();
        super.setEnabled(newState);
        /*
         * Don't enable (or disable) before it's initialized.
         */
        if (!isInitialized()) {
            return;
        }

        if (!oldState && newState) {
            mapButtons();

        } else if (oldState && !newState) {
            unmapButtons();
        }
    }
    // *************************************************************************
    // private methods

    /**
     * Map the mouse buttons used for scene navigation.
     */
    private void mapButtons() {
        assert isInitialized();
        /*
         * In scenes, the left button moves the 3D cursor
         * and aims the orbting POV.
         */
        MouseButtonTrigger left =
                new MouseButtonTrigger(MouseInput.BUTTON_LEFT);
        inputManager.addMapping("aim", left);
        inputManager.addListener(this, "aim");
        /*
         * In scenes, the middle button controls the POV.
         */
        String signalName = PointOfView.controlSignalName;
        String actionString = String.format("signal %s 0", signalName);
        MouseButtonTrigger middle =
                new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE);
        inputManager.addMapping(actionString, middle);
        inputManager.addListener(signals, actionString);
        signals.add(signalName);
        /*
         * In scenes, the right button picks/unpicks blocks, props, or units.
         */
        MouseButtonTrigger right =
                new MouseButtonTrigger(MouseInput.BUTTON_RIGHT);
        inputManager.addMapping("pick", right);
        inputManager.addListener(this, "pick");
    }

    /**
     * Delete the mouse button mappings used for scene navigation.
     */
    private void unmapButtons() {
        inputManager.deleteMapping("aim");

        String signalName = PointOfView.controlSignalName;
        String actionString = String.format("signal %s 0", signalName);
        inputManager.deleteMapping(actionString);

        inputManager.deleteMapping("pick");
    }
}